package com.woniuxy.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniuxy.entity.User;
import com.woniuxy.service.UserService;
import com.woniuxy.utils.JWTUtil;
import com.woniuxy.utils.ResponseResults;
import com.woniuxy.utils.ResponseState;
import com.woniuxy.utils.TokenEnum;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 该过滤器在每次请求时，手动创建Authentication对象，并放入SpringSecurity的上下文中，以便通过Security登录认证
 *
 * 请求需要通过两个认证，第一个是 1）验证请求的uri是否需要登录认证，第二个是 2）登录认证
 * */
@Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {  // OncePerRequestFilter类 一次请求前过滤器

    @Resource
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {

        /** 验证uri是否需要登录认证 */
        //获取请求的URI
        String uri = request.getRequestURI();
        if (!requireAuth(uri)){
            // 直接放行，无需认证
            filterChain.doFilter(request,response);
            return;
        }

        /** 需要认证 进入登录认证流程 */
        // 1. 获取token
        String token = request.getHeader("authorization");
        // 2. 判断  token不为空
        if (token!=null && !token.equals("") && !token.equals("null")){
            // 校验 过期  伪造
            if (JWTUtil.verify(token)== TokenEnum.TOKEN_SUCCESS){
                System.out.println("创建UsernamePasswordAuthenticationToken");
                // 成功 得到id
                int getuid = JWTUtil.getuid(token);
                // 得到用户信息：基本信息，角色信息，权限信息
                User user = userService.findByUid(getuid);

                // TODO 通过用户信息生成Authentication对象
                UsernamePasswordAuthenticationToken authentication =
                        // getAuthorities()封装在User实体类中，调用这个方法获取权限信息
                        new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());

                // TODO 放入上下文中
                // Security框架在SecurityContext中找Authentication对象，因此需要将authentication放入其中
                SecurityContextHolder.getContext().setAuthentication(authentication);

                // 过滤器放行
                System.out.println("放行");
                filterChain.doFilter(request,response);
                return;
            }
        }
        // 3. 代码执行到此处说明token无效
        ResponseResults<Boolean> responseResults = new ResponseResults<Boolean>()
                .setCode(401)
                .setState(ResponseState.NO_LOGIN)
                .setMsg("请登录");
        // 4. 使用流将responseResult写出
        String json = new ObjectMapper().writeValueAsString(responseResults);
        response.setHeader("Content-Type","application/json;charset=utf-8");
        response.getWriter().write(json);
    }


    // 该方法用来验证当前请求是否需要认证才能访问，返回true表示需要
    private boolean requireAuth(String uri) {
        // 将所有不需要认证就能访问的rul写出来
        List<String> noAuthUris = Arrays.asList(
                "/auth/login",
                "/user/regist",
//                "/goods/find",
//                "/goods/update",
//                "/goods/del",
//                "/goods",
//                "/goods/get",
                "/images/goods",
                "/order/pay/result",
                "/file/upload"
        );
        // 所有URI默认都需要认证
        AtomicBoolean flag = new AtomicBoolean(true);
        noAuthUris.forEach(str -> {
            if (uri.equals(str) || uri.startsWith(str)) {
                // 修改
                flag.set(false);
                return;
            }
        });
        // 如果此处返回的是false，值被修改，说明forEach中有uri匹配上了，需要放行
        return flag.get();
    }
}
